﻿/*
 * DATAFLOWCORE
 * 
Copyright 2012 - Mindstorm Multitouch Limited

Author - Bertrand Nouvel

DataFlowCore is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

DataFlowCore is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser Public License for more details.
*/



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;

namespace DFlow
{

    public abstract class Node : IDisposable
    {


        #region AUTOREGISTERED_PLUGIN_TYPE

        static private List<System.Type> _AllNodeTypes = null;
        static private void RecomputeAllNodeTypes()
        {
            DFlowCore.Log.Debug("Recomputing all node types");
            _AllNodeTypes = new List<System.Type>();
            foreach (System.Reflection.Assembly a in AppDomain.CurrentDomain.GetAssemblies())
            {
                try
                {
                    foreach (System.Type t in a.GetExportedTypes())
                    {
                        if (t.IsSubclassOf(typeof(Node)))
                        {
                            if (!t.IsAbstract)
                            {
                                System.Diagnostics.Debug.Assert(!_AllNodeTypes.Contains(t));
                                _AllNodeTypes.Add(t);
                            }
                        }
                    }
                }
                catch (System.NotSupportedException) { }
            }
        }




        static public List<System.Type> AllNodeTypes
        {
            get
            {
                if (_AllNodeTypes == null)
                {
                    AppDomain.CurrentDomain.AssemblyLoad += (e, x) => { RecomputeAllNodeTypes(); };
                    RecomputeAllNodeTypes();
                }
                return _AllNodeTypes;
            }
        }

        static public Type FindNodeType(string s)
        {
            int idx=s.IndexOf('(');
            if (idx!=-1)
            {
                string plugin = s.Substring(idx + 1, s.Length - 2);
                s = s.Substring(0, idx);
                DFlowCore.Engine.Current.PluginFindAndLoad(plugin);
            }
            return AllNodeTypes.Find(nt => nt.Name.Contains(s));
        }



        public static DFlow.Node Create(System.Type t, string nodename)
        {
            if (t == null)
            {
                throw new ArgumentNullException();
            }
            object nodeinstance = t.InvokeMember(null,
                    System.Reflection.BindingFlags.DeclaredOnly |
                    System.Reflection.BindingFlags.Public |
                    System.Reflection.BindingFlags.NonPublic |
                    System.Reflection.BindingFlags.Instance |
                    System.Reflection.BindingFlags.CreateInstance, null, null, new object[] { });


            DFlow.Node nodei = nodeinstance as DFlow.Node;
            nodei.name = nodename;
            return nodei;
        }


        public static DFlow.Node Create(System.Type t, string nodename, BsonDocument configuration)
        {
            DFlow.Node nodei = Create(t,nodename);
            if (configuration != null)
            {
                nodei.UpdateParameters(configuration);
            }
            return nodei;
        }

        public static DFlow.Node Create(System.Type t, string nodename, object configuration)
        {
            DFlow.Node nodei = Create(t, nodename);
            if (configuration != null)
            {
                nodei.UpdateParameters(configuration);
            }
            return nodei;
        }

        #endregion


        public string name;
        public Dataflow host_Dataflow = null;
        protected object parameters;

        List<NodeMonitor> monitors = new List<NodeMonitor>();
        List<Wire> inbound_wires = new List<Wire>();
        List<Wire> outbound_wires = new List<Wire>();


        public virtual Type GetInputType(string s)
        {
            if (s[0] == '*')
            {
                return GetParameters().GetType().GetField(s.Substring(1)).FieldType;
            }
            else
            {
                return GetType().GetField(s).FieldType;
            }
        }

        public virtual Type GetOutputType(string s)
        {
            if (s[0] == '*')
            {
                return GetParameters().GetType().GetField(s.Substring(1)).FieldType;
            }
            else
            {
                return GetType().GetField(s).FieldType;
            }
        }





        public IEnumerable<NodeMonitor> GetMonitors()
        {
            if (monitors == null)
            {
                monitors = new List<NodeMonitor>();
            }
            return monitors;
        }


        public IEnumerable<Wire> GetInboundWires(bool include_crossdataflow=true)
        {
            if (inbound_wires == null)
            {
                inbound_wires = new List<Wire>();
            }
            if (include_crossdataflow)
            {
                return inbound_wires;
            }
            else
            {
                return inbound_wires.Where(w => w.crossdataflow_wire == false);
            }
        }

        public IEnumerable<Wire> GetOutboundWires(bool include_crossdataflow = true)
        {
            if (outbound_wires == null)
            {
                outbound_wires = new List<Wire>();
            }
            if (include_crossdataflow)
            {
                return outbound_wires;
            }
            else
            {
                return outbound_wires.Where(w => w.crossdataflow_wire == false);
            }
        }

        public IEnumerable<NetWire> GetOutboundNetWires()
        {
            foreach (NodeMonitor nm in GetMonitors())
            {
                if (nm is NetWire)
                {
                    yield return (nm as NetWire);
                }
            }
        }


        public IEnumerable<string> ListInputNames(bool includeParameters=false)
        {
            System.Type t = this.GetType();

            foreach (System.Reflection.FieldInfo pi in t.GetFields())
            {
                foreach (object a in pi.GetCustomAttributes(typeof(InputPin), true))
                {
                    //InputPin ip = a as InputPin;
                    yield return pi.Name;
                }
            }

            if (includeParameters)
            {
                foreach (System.Reflection.FieldInfo pi in GetParameters().GetType().GetFields())
                {
                        yield return "*"+pi.Name;
                }
            }
        }


        public IEnumerable<string> ListOutputNames(bool includeParameters = false)
        {
            System.Type t = this.GetType();

            foreach (System.Reflection.FieldInfo pi in t.GetFields())
            {
                foreach (object a in pi.GetCustomAttributes(typeof(OutputPin), true))
                {
                    //OutputPin ip = a as OutputPin;
                    yield return pi.Name;
                }
            }
            if (includeParameters)
            {
                foreach (System.Reflection.FieldInfo pi in GetParameters().GetType().GetFields())
                {
                    yield return "*" + pi.Name;
                }
            }

        }


        ~Node()
        {
            if (outbound_wires != null)
            {
                foreach (Wire w in outbound_wires)
                {
                    w.dstnode.RemoveInboundWire(w);
                }
            }
        }

        public void AddMonitor(NodeMonitor m)
        {
            if (monitors == null) { 
                monitors = new List<NodeMonitor>(); 
            }
            monitors.Add(m);
        }

        public void AddWire(DFlow.Wire w)
        {
            if (w.srcnode != this)
            {
                throw new System.Exception("Wire not registered on the valid node...");
            }

            if (monitors == null) { monitors = new List<NodeMonitor>(); }
            if (inbound_wires == null) { inbound_wires = new List<Wire>(); }
            if (outbound_wires == null) { outbound_wires = new List<Wire>(); }

            DFlow.OutputPin ipp = w.srcnode.FindOutput(w.srcpin);
            DFlow.InputPin opp = w.dstnode.FindInput(w.dstpin);
            if (ipp == null) { throw new System.Exception("Source pin not found"); }
            if (opp == null) { throw new System.Exception("Destination pin not found"); }
            w.srcnode.outbound_wires.Add(w);
            w.dstnode.inbound_wires.Add(w);
            AddMonitor(w);
            w.srcnode.OnAddWire(w);
            w.dstnode.OnAddWire(w);

            foreach (NodeMonitor nm in monitors) { nm.OnAddWire(w); }
            foreach (NodeMonitor nm in w.dstnode.monitors) { nm.OnAddWire(w); }
        }

        public void RemoveWire(Wire w)
        {
            this.inbound_wires.RemoveAll(x => x == w);
            this.outbound_wires.RemoveAll(x => x == w);
            this.monitors.RemoveAll(x => x == w);


            OnRemoveWire(w);
            foreach (NodeMonitor nm in monitors) { nm.OnRemoveWire(w); }
        }

        public void RemoveNetWire(string id)
        {
            this.monitors.RemoveAll(x => (x is NetWire) && ((x as NetWire).id == id));
        }

        public virtual void OnAddWire(DFlow.Wire w) { }
        public virtual void OnRemoveWire(DFlow.Wire w) { }


        public void RemoveInboundWire(Wire w)
        {
            this.inbound_wires.RemoveAll(x => x == w);
        }

        public InputPin FindInput(string name)
        {
            System.Type t = this.GetType();
            foreach (System.Reflection.FieldInfo pi in t.GetFields())
            {
                foreach (object a in pi.GetCustomAttributes(typeof(InputPin), true))
                {
                    InputPin ip = a as InputPin;
                    if (pi.Name == name)
                    {
                        return ip;
                    }
                }
            }
            return null;
        }

        public OutputPin FindOutput(string name)
        {
            System.Type t = this.GetType();

            foreach (System.Reflection.FieldInfo pi in t.GetFields())
            {
                foreach (object a in pi.GetCustomAttributes(typeof(OutputPin), true))
                {
                    OutputPin ip = a as OutputPin;
                    if (pi.Name == name)
                    {
                        return ip;
                    }
                }
            }
            return null;
        }


        public IEnumerable<System.Reflection.FieldInfo> ListOutputPins()
        {
            System.Type t = this.GetType();

            foreach (System.Reflection.FieldInfo pi in t.GetFields())
            {
                foreach (object a in pi.GetCustomAttributes(typeof(OutputPin), true))
                {
                    //OutputPin ip = a as OutputPin;
                    yield return pi;
                }
            }
        }


        public IEnumerable<System.Reflection.FieldInfo> ListOutputMetadataPins()
        {
            System.Type t = this.GetType();

            foreach (System.Reflection.FieldInfo pi in t.GetFields())
            {
                foreach (object ta in pi.GetCustomAttributes(typeof(MetadataPin), true))
                {
                    foreach (object a in pi.GetCustomAttributes(typeof(OutputPin), true))
                    {
                        //OutputPin ip = a as OutputPin;
                        yield return pi;
                    }
                }
            }
        }

        public IEnumerable<System.Reflection.FieldInfo> ListInputPins()
        {
            System.Type t = this.GetType();

            foreach (System.Reflection.FieldInfo pi in t.GetFields())
            {
                foreach (object a in pi.GetCustomAttributes(typeof(InputPin), true))
                {
                    //InputPin ip = a as InputPin;
                    yield return pi;
                }
            }
        }


        public IEnumerable<System.Reflection.FieldInfo> ListInputMetadataPins()
        {
            System.Type t = this.GetType();

            foreach (System.Reflection.FieldInfo pi in t.GetFields())
            {
                foreach (object ta in pi.GetCustomAttributes(typeof(MetadataPin), true))
                {
                    foreach (object a in pi.GetCustomAttributes(typeof(InputPin), true))
                    {
                        //OutputPin ip = a as OutputPin;
                        yield return pi;
                    }
                }
            }
        }



        public void CheckInputs()
        {
            //#if WITH_INSTANTIATE_INPUTS
            // required by generic list converters apparently... that prefer to use a already instantiated object
            // rather to have to instantiate an unknown type object...

            System.Type t = this.GetType();

            foreach (System.Reflection.FieldInfo pi in t.GetFields())
            {
                foreach (object a in pi.GetCustomAttributes(typeof(InputPin), true))
                {
                    // we don't instantiate useless dummy objects "null" is better
                    if ((pi.GetValue(this) == null) && (pi.FieldType != typeof(object)))
                    {
                        //throw new System.Exception("Not yet implemented");
                        try
                        {
                            pi.SetValue(this, pi.FieldType.InvokeMember(null, System.Reflection.BindingFlags.CreateInstance, null, null, null));
                        }
                        catch (System.Exception)
                        {
                            // System.Diagnostics.Debug.Print(e.ToString());
                        }
                    }
                }
            }

            // check no input is null 
            //#endif
        }
        public void CheckOutputs()
        {
#if WITH_INSTANTIATE_OUTPUTS
            System.Type t = this.GetType();

            foreach (System.Reflection.FieldInfo pi in t.GetFields())
            {
                foreach (object a in pi.GetCustomAttributes(typeof(OutputPin), true))
                {
                    if (pi.GetValue(this) == null)
                    {
//                        throw new System.Exception("Not yet implemented");
                        try {
                            pi.SetValue(this,pi.FieldType.InvokeMember(null,System.Reflection.BindingFlags.CreateInstance,null,null,null));
                        }
                        catch (System.Exception /*e*/) {
                            //System.Diagnostics.Debug.Print(e.ToString());
                        }

                    }
                }
            }
            // check no input is null 
#endif
        }

        /*
        public enum NodeState
        {
            Ready,
            RequiresTraining,
            Error,
            Unknown
        }

        public virtual bool RequiresTraining() {return false;}
         */
        public virtual bool IsReady() { return true; }

        /*
        public virtual NodeState GetNodeState() {
            bool r=IsReady();
            bool t=RequiresTraining();
            if ((r)&&(!t)) {return NodeState.Ready;}
            if (t) { return NodeState.RequiresTraining; }
            return NodeState.Error;
        }
         */





        public bool IsInputPinConnected(string pinname)
        {
            foreach (Wire w in GetInboundWires())
            {
                if (w.dstpin == pinname)
                {
                    return true;
                }
            }
            return false;
        }

        public bool IsOutputPinConnected(string pinname)
        {
            foreach (Wire w in GetOutboundWires())
            {
                if (w.srcpin == pinname)
                {
                    return true;
                }
            }
            return false;
        }

        public virtual bool IsInputPinCompatibleWith(string pinname, Type t)
        {
            return (Converters.GetConverter(t, GetInputType(pinname)) != null);
        }

        public virtual bool IsOutputPinCompatibleWith(string pinname, Type t)
        {
            return (Converters.GetConverter(GetOutputType(pinname), t) != null);
        }

        public abstract void Process();
        public virtual object DefaultParameters()
        {
            return this.GetType().GetNestedType("Parameters").InvokeMember(null, System.Reflection.BindingFlags.CreateInstance, null, null, null);
        }
        //public string [] GetMonitors();


        public object GetParameters()
        {
            if (parameters == null)
            {
                parameters = DefaultParameters();
            }
            return parameters;
        }


        public virtual void UpdateParameters(object o)
        {
            _UpdateParameters((BsonDocument)o);
        }


        private void _UpdateParameters(BsonDocument doc)
        {
            //System.Type pt = this.GetType().GetNestedType("Parameters");
            //parameters=MongoDB.Bson.Serialization.BsonSerializer.Deserialize(doc, pt);
            if (parameters == null) { parameters = DefaultParameters(); }
            if (doc != null)
            {
                DFlowCore.BsonUtils.UpdateObject(parameters, doc);
            }
        }

        public string GetNodetype()
        {
            System.Type tp = GetType();
            return ((tp.Assembly.GetCustomAttributes(typeof(System.Reflection.AssemblyTitleAttribute), false))[0] as System.Reflection.AssemblyTitleAttribute).Title + "::" + tp.Name;
        }


        public virtual void OnDataflowChanged()
        {
            // may be useful to identify upward  / downward config changes
            // maybe useful to compute config hash

            UnconnectInputMetadataPins();
            TryConnectUnconnectedInputMetadataPins();
        }

        public List<Node> GetPredecessorsInDataflow()
        {
            List<Node> r = new List<Node>();
            bool addsome = false;
            foreach (Wire w in GetInboundWires())
            {
                if (!r.Contains(w.srcnode))
                {
                    r.Add(w.srcnode);
                    addsome = true;
                }
            }

            while (addsome)
            {
                addsome = false;
                int c = r.Count;
                for (int i = 0; i < c; i++)
                {
                    Node n = r[i];
                    foreach (Wire w in n.GetInboundWires())
                    {
                        if (!r.Contains(w.srcnode))
                        {
                            r.Add(w.srcnode);
                            addsome = true;
                        }
                    }

                }
            }

            return r;
        }


        public BsonDocument GetPredecessorsStates()
        {
            BsonDocument bd = new BsonDocument();
            foreach (Node n in GetPredecessorsInDataflow())
            {
                bd.Add(n.name, DFlowCore.BsonUtils.BsonEncode(n.GetParameters(), null));
            }
            return bd;
        }

        public byte[] GetPredecessorsStateHashBytes()
        {

            System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] hash = md5.ComputeHash(DFlowCore.BsonUtils.Encode(GetPredecessorsStates()));
#if !__MonoCS__
			md5.Dispose();
#endif
            return hash;
        }

        public string GetPredecessorsStateHashString()
        {
            // step 2, convert byte array to hex string
            byte[] hash = GetPredecessorsStateHashBytes();
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("X2"));
            }
            return sb.ToString();
        }

        public List<Node> GetSuccessorsInDataflow()
        {
            List<Node> r = new List<Node>();
            bool addsome = false;
            foreach (Wire w in GetOutboundWires())
            {
                if (!r.Contains(w.dstnode))
                {
                    r.Add(w.dstnode);
                    addsome = true;
                }
            }

            while (addsome)
            {
                addsome = false;
                int c = r.Count;
                for (int i = 0; i < c; i++)
                {
                    Node n = r[i];
                    foreach (Wire w in n.GetOutboundWires())
                    {
                        if (!r.Contains(w.dstnode))
                        {
                            r.Add(w.dstnode);
                            addsome = true;
                        }
                    }

                }
            }

            return r;
        }


        public IEnumerable<Pair<Node, System.Reflection.FieldInfo>> EnumerateCompatiblePins(string pinname)
        {
            foreach (Node n in GetPredecessorsInDataflow())
            {
                if (n != this)
                {
                    foreach (System.Reflection.FieldInfo p in n.ListOutputMetadataPins())
                    {
                        if (p.Name == pinname)
                        {
                            yield return new Pair<Node, System.Reflection.FieldInfo>
                               (n, p);
                        }
                    }

                }
            }
        }


        public IEnumerable<Pair<Node, System.Reflection.FieldInfo>>
            EnumerateCompatibleOutpinsPinsWithUpwardDataflow(string pinname)
        {
            foreach (Pair<Node, System.Reflection.FieldInfo> p in this.EnumerateCompatiblePins(pinname))
            {
                yield return p;
            }
            if (host_Dataflow.parent_Dataflow != null)
            {
                if (this.host_Dataflow.parent_Node == null)
                {
                    foreach (DFlow.Node x in
                        this.host_Dataflow.parent_Dataflow.GetNodes())
                    {
                        foreach (System.Reflection.FieldInfo pi
                            in x.ListOutputMetadataPins().Where(n => n.Name == pinname))
                        {
                            yield return new DFlow.Pair<Node, System.Reflection.FieldInfo>(x, pi);
                        }
                    }
                }
                else
                {
                    foreach (var p in host_Dataflow.parent_Node.EnumerateCompatibleOutpinsPinsWithUpwardDataflow(pinname))
                    {
                        yield return p;
                    }
                }
            }
        }


        // for upward it may be useful to know in which node we are supposed to  be instantiated into the 
        // the host pipeline        
        public void UnconnectInputMetadataPins()
        {
            System.Collections.Generic.List<Wire> toberemoved = new System.Collections.Generic.List<Wire>();
            foreach (var pin in ListInputMetadataPins())
            {
                foreach (Wire w in GetInboundWires())
                {
                    if ((w.metadata_wire) && (w.dstpin == pin.Name))
                    {
                        toberemoved.Add(w);
                    }
                }
            }

            foreach (Wire w in toberemoved)
            {
                w.srcnode.RemoveWire(w);
            }
        }


        public void TryConnectUnconnectedInputMetadataPins()
        {
            foreach (var pin in ListInputMetadataPins())
            {
                if (!this.IsInputPinConnected(pin.Name))
                {
                    foreach (DFlow.Pair<Node, System.Reflection.FieldInfo> p in this.EnumerateCompatibleOutpinsPinsWithUpwardDataflow(pin.Name))
                    {
                        Wire w = new Wire(p.first, p.second.Name, this, pin.Name);
                        w.metadata_wire = true;
                        p.first.AddWire(w);
                    }
                }
            }
        }






        public virtual void Dispose()
        {
        }
    }


}
